在開始之前,先來看看以下的例子
function Example() {
const [height, setHeight] = useState(50);
useEffect(() => {
if (height === 50) {
setHeight(100);
}
}, [height]);
return (
<>
<div style={{ width: 100, height, background: "hotpink" }}/>
<button onClick={() => setHeight(50)}>change height!</button>
</>
);
}
點擊 button 後,你會發現
咦! 上面那塊正方形怎麼會閃爍
在這個例子中我們使用 useEffect
讓 height 更新後再去更新一次,觸發 re-render
理論上兩次 setState 應該會 batch 在一起,只 render 一次呀
我們可以先看一下 Docs 上怎麼說
Effect 的時機
與 componentDidMount 和 componentDidUpdate 不同,在延遲事件期間,傳遞給 useEffect 的 function 會在 layout 和 render 之後觸發。
沒錯,如同上面所述,useEffect
的觸發時機會被延遲到 DOM 繪制完成。因此我們點擊按鈕 setState 後,其實已經經過 render -> commit DOM -> DOM render 的過程,所以才會被看到中途 render 完後的過程。正因為如此,肉眼才會看到中間過渡的 state 導致有閃爍的感覺。
節錄自 Docs
useLayoutEffect
會在所有 DOM 改變後,同步調用。使用它來讀取 DOM layout 並同步重新 render。
在瀏覽器執行繪製之前,useLayoutEffect 內部的更新將被同步刷新。
正因為這個 hook 的特性,我們可以使用它來讓 DOM 的渲染慢一拍,等待 state 真正更新完後才去渲染瀏覽器的畫面。
要注意的是,使用
useLayoutEffect
會導致渲染被阻塞,所以不要拿來跑太複雜的計算
正如同文件上所說,決大部分的場景,都不應該阻礙瀏覽器更新晝面。
今天的主題雖然蠻簡單的,但應該還算實用,不過最好的解法應該是不要在 useEffect 監聽 state 再去 setState,避免 render 太多次。
前端工程師一起來種一棵後端技能樹吧!
想盡辦法當好一個Junior Backend Developer